/*
	Copyright (C) 2014 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with illusion.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <arch/asmdecl.h>
#include <arch/multiboot.h>
#include <kernel/console.h>
#include <kernel/errors.h>
#include <kernel/module.h>
#include <kernel/ilapp.h>
#include <klib/string.h>
#include <klib/memory.h>
#include <klib/format.h>
#include <kernel/page.h>
#include <kernel/vmpage.h>
#include <kernel/init.h>
#include <klib/lib.h>
#include <arch/power.h>
#include <kernel/kmalloc.h>
#include <kernel/vfs.h>

//TODO Do not map everything from the zero to 1 MB by default. This means even multiboot_info will have to be mapped

static char no_module = 0;

static int __reserve_module_memory(struct multiboot_module *module)
{
	char *mod_data = (void *)module->mod_start;
	unsigned long size = module->mod_end - module->mod_start;
	unsigned long nr_pages = size / PAGE_SIZE + !!(size % PAGE_SIZE);

	initmsg("Reserve module phys: 0x%x (%u pages)\n", module->mod_start, nr_pages);
	//Module initial physical location
	phys_reserve_pages((void *)module->mod_start, nr_pages);
	return ENONE;
}

int multiboot_reserve_module_memory()
{
	unsigned long module_count = 0;
	initmsg("Checking for modules\n");
	struct multiboot_info *multiboot_info =
			(struct multiboot_info *)_grub_multiboot_info;
	if(!(multiboot_info->flags & FLAG_MODS) ||
			multiboot_info->mods_count == 0) {
		initmsg("No module found\n");
		no_module = 1;
		return ENOENT;
	}
	//Iterate through all the modules,
	//simply reserving phys and vm memory for them
	int i;
	struct multiboot_module *module =
			(struct multiboot_module *)multiboot_info->mods_addr;
	for(i = 0; i < multiboot_info->mods_count; i++) {
		initmsg("Module #%u\n", i);
		int err = __reserve_module_memory(module);
		if(err == 0)
			module_count++;
		module++;
	}
	if(module_count == 0) {
		no_module = 1;
		return ENOENT;
	}
	return ENONE;
}

static int register_module(struct multiboot_module *module, unsigned long index)
{
	char *mod_data_phys = (void *)module->mod_start;
	unsigned long size = module->mod_end - module->mod_start;
	unsigned long nr_pages = size / PAGE_SIZE + !!(size % PAGE_SIZE);
	unsigned long i;
	//Get access to the module data
	char *mod_data = vm_get_virt_for_phys(&kernel_vmmap, mod_data_phys,
			nr_pages, PAGE_PRESENT | PAGE_WRITE);

	initmsg("Registering module @ 0x%x\n", mod_data_phys);

	struct vfs_node *node;
	vfs_get(&node, null, "illusion:/bootmodules");

	char mod_name[32];
	format_string(mod_name, 32, "module%u", index);

	//Write the data to the RAM file system
	struct vfs_node *mod_fs = vfs_create(node, mod_name);

	vfs_write(mod_fs, "data", mod_data, 0, size);

	//Free the mod_data pages
	vm_free_pages(&kernel_vmmap, mod_data, nr_pages);

	return ENONE;
}

int multiboot_register_modules()
{
	if(no_module) {
		initmsg("Skipping: No modules were found in earlier phase\n");
		return ENOENT;
	}
	initmsg("Registering modules\n");
	struct multiboot_info *multiboot_info =
			(struct multiboot_info *)_grub_multiboot_info;
	if(!(multiboot_info->flags & FLAG_MODS) ||
			multiboot_info->mods_count == 0) {
		initmsg("No module found\n");
		return ENOENT;
	}
	//Iterate through all the modules, simply reserving phys and vm memory
	//for them
	int i;
	struct multiboot_module *module =
			(struct multiboot_module *)multiboot_info->mods_addr;
	for(i = 0; i < multiboot_info->mods_count; i++) {
		initmsg("Module #%u\n", i);
		register_module(module, i);
		module++;
	}
	return ENONE;
}
